<?php
/**
 * NetBiz Framework
 * Copyright (C) 20009 - 2010 NetBiz
 * 
 * All rights reserved.
 */

class FRM_Pager
{
	protected $_iterator = null;
	protected $_arg = null;

	protected $_items_per_page = null;
	protected $_offset = null;
	protected $_count = null;
	protected $_page = null;

	/**
	 * Construct a new pager
	 *
	 * @param string $arg_name, the name of the argument (GET,POST or ARG) that will
	 *   hold the page to use.
	 * @param int $items_per_page, the number of items to display on one page,
	 *   constained to positive integer.
	 * @param FRM_Pager_IteratorInterface $iterator, the iterator to use to enumerate
	 *   pages. If null, a default one is constructed.
	 */
	public function __construct($arg_name, $items_per_page, FRM_Pager_IteratorInterface $iterator = null)
	{
		$this->_arg = $arg_name;
		// Constraining items_per_page in N*
		$this->_items_per_page = max(1,intval($items_per_page));

		// Construct a default iterator if not specified
		if (!$iterator)
		{
			$iterator = new FRM_Pager_RangeIterator();
		}

		$this->_iterator = $iterator;
	}

	/**
	 * Activate the paging for a FRM_DataObject.
	 * Inversion of command, the pager is called with the
	 * object as parameter, and call it back to limit the
	 * selection of items.
	 *
	 * If a sql query is specified, it will be modified
	 * to include a limit clause then returned (the object
	 * won't be called, but just used for issuing counting
	 * query)
	 *
	 * @note The value of $arg_name parameter is obtained
	 *   only in this function 
	 * @param FRM_DataObject the object to paginate
	 * @param string the optionnal sql query string to limit
	 *   Ignored if NULL
	 * @return a modified SQL query with a limit clause if
	 *   the second parameter was specified, TRUE otherwise
	 */
	public function page(FRM_DataObject $obj, $sql_query = NULL)
	{
		// First, counting the number of records
		// Only if it hasn't been calculated yet
		if (is_null($this->_count))
		{
			$count = $this->count($obj,$sql_query);
			list($offset,$limit) = $this->calculate($count);
		}
		else
		{
			list($offset,$limit) = $this->getLimit();
		}

		if (is_null($sql_query))
		{
			$obj->limit($offset,$limit);
			return TRUE;
		}
		else
		{
			return "SELECT * FROM ( $sql_query ) _tblLimit LIMIT $offset,$limit";
		}
	}


	/**
	 * Count the number of records returned by an object or
	 * a query string.
	 *
	 * @param FRM_DataObject the object to count records on
	 * @param string optionnal query string, if specified (non-null),
	 *   we count the number of records returned by this query
	 *   and use the first parameter as the issuer of the query
	 * @return int the number of records counted
	 */
	public function count(FRM_DataObject $obj, $sql_query = NULL)
	{
		// Depending on second parameter, we use the query string or the object
		// structure for the count
		if (is_null($sql_query))
		{
			// :TODO: InnoDB count is way too slow, can we extract an flag
			// from DB dataobject ini files (embedded in class) so we know which
			// one is my isam and which one is innodb?
			// For innodb, we can use PK cardinality hint in index list for a
			// approximative pagination, then adapting the RangeIterator might
			// be a good option, with a specific 0 last range
			$obj_count = clone($obj);
			return $obj_count->count();
		}
		else
		{
			// Building a safe query
			$count_query = "SELECT COUNT(1) as count FROM ( $sql_query ) _tblCount";

			// using the target object class
			$class = get_class($obj);
			$counter = new $class;
			$counter->query($count_query);

			if ($counter->fetch())
			{
				return $counter->count;
			}
			else
			{
				return FALSE;
			}
		}
	}

	/**
	 * Calculate internal pager values based on a count of object
	 * Needs to be called before @link FRM_Pager::getLimit or 
	 * or @link FRM_Pager::getData can be safely called
	 *
	 * @param int the count of element for configuring the pager
	 * @return @see FRM_Pager::getLimit
	 */
	public function calculate($count)
	{
		// Use the count of object to build internal pager variables
		$this->_count = ceil($count / $this->_items_per_page);
		// Get the page from the request handler
		// constrained to 1,ceil($count/$this->_items_per_page)
		$this->_page = max(1,min($this->_count,FRM_Request::getIntNumber($this->_arg,1)));
		// Initialize the iterator
		$this->_iterator->initialize($this->_page,$this->_count);

		// We're good to limit
		// - using object method OR
		// - modifying the sql query
		$this->_offset = ($this->_page-1)*$this->_items_per_page;

		return $this->getLimit();
	}

	/**
	 * Return the representation of the limit clause of an active
	 * pager. Under the form [offset, limit]
	 *
	 * @return array an array with the first element being the offset
	 *   and the second the number of item.
	 */
	public function getLimit()
	{
		return array($this->_offset,$this->_items_per_page);
	}

	/**
	 * Returns an associative array with all pagination info for the object
	 *
	 * Elements in the results array :
	 *   - 'current' : int, the current page index
	 *   - 'previous' : mixed, the previous page index, FALSE if none
	 *   - 'next' : mixed, the next page index, FALSE if none
	 *   - 'first' : bool, TRUE if the current page is the first one ( = !previous)
	 *   - 'last' : bool, TRUE if the current page is the last one ( = !next)
	 *   - 'total' : int, the total number of page
	 *   - 'arg' : string, the argument name for the page index
	 *   - 'iter' : An iterator on the pages that should be displayed, @see FRM_Pager_IteratorInterface
	 *
	 * @return array
	 */
	public function getData()
	{
		return array(
			'current' => $this->_page,
			'previous' => ($this->_page > 1 ? $this->_page - 1 : FALSE),
			'next' => ($this->_page < $this->_count ? $this->_page + 1 : FALSE),
			'first' => ($this->_page > 1 ? 1 : FALSE),
			'last' => ($this->_page < $this->_count ? $this->_count : FALSE),
			'total' => $this->_count,
			'arg' => $this->_arg,
			'iter' => $this->_iterator,
		);
	}
}